package cn.edu.scau.cmi.ema.service;

import cn.edu.scau.cmi.ema.base.CmiPagination;
import cn.edu.scau.cmi.ema.domain.*;
import cn.edu.scau.cmi.ema.service.base.CmiEmaService;
import cn.edu.scau.cmi.ema.util.CmiSqlUtil;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.Query;
import java.util.*;
@Service("MaterialService")
@Transactional

public class MaterialServiceImpl extends CmiEmaService implements MaterialService {
	public MaterialServiceImpl() {
	}

	@Transactional
	public Material saveMaterial(Material material) {
		Set<Foodbatchmaterial> existingFoodbatchmaterialsForMaterial = new HashSet<Foodbatchmaterial>();
		Set<Foodmaterial> existingFoodmaterialsForMaterial = new HashSet<Foodmaterial>();
		Set<Materialaddress> existingMaterialaddresssForMaterial = new HashSet<Materialaddress>();
		Set<Purchasematerial> existingPurchasematerialsForMaterial = new HashSet<Purchasematerial>();

		Set<Foodbatchmaterial> selectedFoodbatchmaterialsForMaterial = material.getFoodbatchmaterialsForMaterial();
		Set<Foodmaterial> selectedFoodmaterialsForMaterial = material.getFoodmaterialsForMaterial();
		Set<Materialaddress> selectedMaterialaddresssForMaterial = material.getMaterialaddresssForMaterial();
		Set<Purchasematerial> selectedPurchasematerialsForMaterial = material.getPurchasematerialsForMaterial();

		if (material.getId() == null) {

			Material existingMaterial = materialDAO.findMaterialByPhysicalPrimaryKey(material.getName(),
					material.getQuality());

			if (isEquals(material, existingMaterial)) {
				return existingMaterial;
			}

			material = materialDAO.store(material);
			materialDAO.flush();

			for (Foodbatchmaterial relatedFoodbatchmaterial : selectedFoodbatchmaterialsForMaterial) {
				relatedFoodbatchmaterial.setMaterial(material);
				foodbatchmaterialDAO.store(relatedFoodbatchmaterial);
			}
			for (Foodmaterial relatedFoodmaterial : selectedFoodmaterialsForMaterial) {
				relatedFoodmaterial.setMaterial(material);
				foodmaterialDAO.store(relatedFoodmaterial);
			}
			for (Materialaddress relatedMaterialaddress : selectedMaterialaddresssForMaterial) {
				relatedMaterialaddress.setMaterial(material);
				materialaddressDAO.store(relatedMaterialaddress);
			}
			for (Purchasematerial relatedPurchasematerial : selectedPurchasematerialsForMaterial) {
				relatedPurchasematerial.setMaterial(material);
				purchasematerialDAO.store(relatedPurchasematerial);
			}
		} else {
			Material existingMaterial = materialDAO.findMaterialByPrimaryKey(material.getId());

			existingFoodbatchmaterialsForMaterial = existingMaterial.getFoodbatchmaterialsForMaterial();
			existingFoodmaterialsForMaterial = existingMaterial.getFoodmaterialsForMaterial();
			existingMaterialaddresssForMaterial = existingMaterial.getMaterialaddresssForMaterial();
			existingPurchasematerialsForMaterial = existingMaterial.getPurchasematerialsForMaterial();

			Set<Foodbatchmaterial> prepareDeleteFoodbatchmaterialsForMaterial = new HashSet<Foodbatchmaterial>();
			Set<Foodbatchmaterial> prepareAddFoodbatchmaterialsForMaterial = new HashSet<Foodbatchmaterial>();

			prepareDeleteFoodbatchmaterialsForMaterial.addAll(selectedFoodbatchmaterialsForMaterial);
			prepareDeleteFoodbatchmaterialsForMaterial.addAll(existingFoodbatchmaterialsForMaterial);

			prepareAddFoodbatchmaterialsForMaterial.addAll(selectedFoodbatchmaterialsForMaterial);
			prepareAddFoodbatchmaterialsForMaterial.addAll(existingFoodbatchmaterialsForMaterial);

			prepareDeleteFoodbatchmaterialsForMaterial.removeAll(selectedFoodbatchmaterialsForMaterial);
			prepareAddFoodbatchmaterialsForMaterial.removeAll(existingFoodbatchmaterialsForMaterial);

			for (Foodbatchmaterial relatedFoodbatchmaterial : prepareAddFoodbatchmaterialsForMaterial) {
				relatedFoodbatchmaterial.setMaterial(material);
				foodbatchmaterialDAO.store(relatedFoodbatchmaterial);
			}

			for (Foodbatchmaterial relatedFoodbatchmaterial : prepareDeleteFoodbatchmaterialsForMaterial) {
				relatedFoodbatchmaterial.setMaterial(null);
				foodbatchmaterialDAO.store(relatedFoodbatchmaterial);
			}
			Set<Foodmaterial> prepareDeleteFoodmaterialsForMaterial = new HashSet<Foodmaterial>();
			Set<Foodmaterial> prepareAddFoodmaterialsForMaterial = new HashSet<Foodmaterial>();

			prepareDeleteFoodmaterialsForMaterial.addAll(selectedFoodmaterialsForMaterial);
			prepareDeleteFoodmaterialsForMaterial.addAll(existingFoodmaterialsForMaterial);

			prepareAddFoodmaterialsForMaterial.addAll(selectedFoodmaterialsForMaterial);
			prepareAddFoodmaterialsForMaterial.addAll(existingFoodmaterialsForMaterial);

			prepareDeleteFoodmaterialsForMaterial.removeAll(selectedFoodmaterialsForMaterial);
			prepareAddFoodmaterialsForMaterial.removeAll(existingFoodmaterialsForMaterial);

			for (Foodmaterial relatedFoodmaterial : prepareAddFoodmaterialsForMaterial) {
				relatedFoodmaterial.setMaterial(material);
				foodmaterialDAO.store(relatedFoodmaterial);
			}

			for (Foodmaterial relatedFoodmaterial : prepareDeleteFoodmaterialsForMaterial) {
				relatedFoodmaterial.setMaterial(null);
				foodmaterialDAO.store(relatedFoodmaterial);
			}
			Set<Materialaddress> prepareDeleteMaterialaddresssForMaterial = new HashSet<Materialaddress>();
			Set<Materialaddress> prepareAddMaterialaddresssForMaterial = new HashSet<Materialaddress>();

			prepareDeleteMaterialaddresssForMaterial.addAll(selectedMaterialaddresssForMaterial);
			prepareDeleteMaterialaddresssForMaterial.addAll(existingMaterialaddresssForMaterial);

			prepareAddMaterialaddresssForMaterial.addAll(selectedMaterialaddresssForMaterial);
			prepareAddMaterialaddresssForMaterial.addAll(existingMaterialaddresssForMaterial);

			prepareDeleteMaterialaddresssForMaterial.removeAll(selectedMaterialaddresssForMaterial);
			prepareAddMaterialaddresssForMaterial.removeAll(existingMaterialaddresssForMaterial);

			for (Materialaddress relatedMaterialaddress : prepareAddMaterialaddresssForMaterial) {
				relatedMaterialaddress.setMaterial(material);
				materialaddressDAO.store(relatedMaterialaddress);
			}

			for (Materialaddress relatedMaterialaddress : prepareDeleteMaterialaddresssForMaterial) {
				relatedMaterialaddress.setMaterial(null);
				materialaddressDAO.store(relatedMaterialaddress);
			}
			Set<Purchasematerial> prepareDeletePurchasematerialsForMaterial = new HashSet<Purchasematerial>();
			Set<Purchasematerial> prepareAddPurchasematerialsForMaterial = new HashSet<Purchasematerial>();

			prepareDeletePurchasematerialsForMaterial.addAll(selectedPurchasematerialsForMaterial);
			prepareDeletePurchasematerialsForMaterial.addAll(existingPurchasematerialsForMaterial);

			prepareAddPurchasematerialsForMaterial.addAll(selectedPurchasematerialsForMaterial);
			prepareAddPurchasematerialsForMaterial.addAll(existingPurchasematerialsForMaterial);

			prepareDeletePurchasematerialsForMaterial.removeAll(selectedPurchasematerialsForMaterial);
			prepareAddPurchasematerialsForMaterial.removeAll(existingPurchasematerialsForMaterial);

			for (Purchasematerial relatedPurchasematerial : prepareAddPurchasematerialsForMaterial) {
				relatedPurchasematerial.setMaterial(material);
				purchasematerialDAO.store(relatedPurchasematerial);
			}

			for (Purchasematerial relatedPurchasematerial : prepareDeletePurchasematerialsForMaterial) {
				relatedPurchasematerial.setMaterial(null);
				purchasematerialDAO.store(relatedPurchasematerial);
			}
			material = materialDAO.store(material);
			materialDAO.flush();
		}

		foodbatchmaterialDAO.flush();
		foodmaterialDAO.flush();
		materialaddressDAO.flush();
		purchasematerialDAO.flush();
		return material;
	}

	public void deleteMaterial(Material material) {
		materialDAO.remove(material);
		materialDAO.flush();
	}

	@Transactional
	public void deleteMaterials(Set<Integer> deleteMaterialsId) {
		Query query = materialDAO.createNamedQuery("deleteMaterialsById", -1, -1);
		query.setParameter(1, deleteMaterialsId);
		query.executeUpdate();
	}

	@Transactional
	public Set<Material> loadMaterials() {
		return materialDAO.findAllMaterials();

	}

	@Transactional
	public Set<Material> loadReferenceMaterials(Set<Material> materials) {
		Iterator<Material> materialsIterator = materials.iterator();
		Set<Material> referencedMaterials = new HashSet<>();
		while (materialsIterator.hasNext()) {
			Material currentMaterial = materialsIterator.next();

			if (!currentMaterial.getFoodbatchmaterialsForMaterial().isEmpty()) {
				referencedMaterials.add(currentMaterial);
				continue;
			}

			if (!currentMaterial.getFoodmaterialsForMaterial().isEmpty()) {
				referencedMaterials.add(currentMaterial);
				continue;
			}

			if (!currentMaterial.getMaterialaddresssForMaterial().isEmpty()) {
				referencedMaterials.add(currentMaterial);
				continue;
			}

			if (!currentMaterial.getPurchasematerialsForMaterial().isEmpty()) {
				referencedMaterials.add(currentMaterial);
				continue;
			}
		}
		return referencedMaterials;
	}

	public Set<Material> loadMaterialsByIdSet(Set<Integer> materialIdSet) {

		Iterator<Integer> materialIdIterator = materialIdSet.iterator();
		Set<Material> materialSet = new HashSet<>();
		while (materialIdIterator.hasNext()) {
			Integer id = materialIdIterator.next();
			Material currentMaterial = materialDAO.findMaterialById(id);
			materialSet.add(currentMaterial);
		}
		return materialSet;
	}

	@Transactional
	public List<Material> findAllMaterials(Integer startResult, Integer maxRows) {
		return new java.util.ArrayList<Material>(materialDAO.findAllMaterials(startResult, maxRows));
	}

	@Transactional
	public Material findMaterialByPrimaryKey(Integer id) {
		return materialDAO.findMaterialByPrimaryKey(id);
	}

	@Transactional
	public Integer countMaterials() {
		return ((Long) materialDAO.createQuerySingleResult("select count(o) from Material o").getSingleResult())
				.intValue();
	}

	@Transactional
	public Integer countMaterials(CmiPagination page) {
		String sql = CmiSqlUtil.createCountHql(page, Material.class);
		return ((Long) materialDAO.createQuerySingleResult(sql).getSingleResult()).intValue();
	}

	/**
	 * search for name with ajax
	 *
	 */
	@Transactional
	public Integer countMaterialsByName(String queryName) {
		Query query = materialDAO.createNamedQuery("countMaterialsByName", -1, -1);
		query.setParameter(1, queryName);
		return new Long((long) query.getSingleResult()).intValue();
	}

	@Transactional
	public Integer countMaterialsByDetachedCriteria(CmiPagination page) {
		DetachedCriteria detachedCriteria = CmiSqlUtil.createDetachedCriteria(page, Material.class);
		Session session = (Session) entityManager.getDelegate();
		detachedCriteria.setProjection(Projections.rowCount());
		return new Long((long) detachedCriteria.getExecutableCriteria(session).uniqueResult()).intValue();
	}

	public Map<String, ?> loadMaterialObjectReferencedSet(Material material) {
		HashMap<String, Object> objectReferencedSetMap = new HashMap<>();

		Set<Integer> foodbatchmaterialsIds = new HashSet<Integer>();
		for (Foodbatchmaterial obj : material.getFoodbatchmaterialsForMaterial()) {

			foodbatchmaterialsIds.add(obj.getId());
		}
		objectReferencedSetMap.put("foodbatchmaterialsIds", foodbatchmaterialsIds);
		Set<Integer> foodmaterialsIds = new HashSet<Integer>();
		for (Foodmaterial obj : material.getFoodmaterialsForMaterial()) {

			foodmaterialsIds.add(obj.getId());
		}
		objectReferencedSetMap.put("foodmaterialsIds", foodmaterialsIds);
		Set<Integer> materialaddresssIds = new HashSet<Integer>();
		for (Materialaddress obj : material.getMaterialaddresssForMaterial()) {

			materialaddresssIds.add(obj.getId());
		}
		objectReferencedSetMap.put("materialaddresssIds", materialaddresssIds);
		Set<Integer> purchasematerialsIds = new HashSet<Integer>();
		for (Purchasematerial obj : material.getPurchasematerialsForMaterial()) {

			purchasematerialsIds.add(obj.getId());
		}
		objectReferencedSetMap.put("purchasematerialsIds", purchasematerialsIds);

		return objectReferencedSetMap;
	}

	public Map<String, ?> loadMaterialClassReferencedSet(Material material) {
		HashMap<String, Object> classReferencedSetMap = new HashMap<>();
		classReferencedSetMap.put("foodbatchmaterials", foodbatchmaterialDAO.findAllFoodbatchmaterials());
		classReferencedSetMap.put("foodmaterials", foodmaterialDAO.findAllFoodmaterials());
		classReferencedSetMap.put("materialaddresss", materialaddressDAO.findAllMaterialaddresss());
		classReferencedSetMap.put("purchasematerials", purchasematerialDAO.findAllPurchasematerials());

		return classReferencedSetMap;
	}

	public boolean isEquals(Material material, Object obj) {
		if (obj == material)
			return true;
		if (!(obj instanceof Material))
			return false;
		Material comparedMaterial = (Material) obj;
		if (!CheckPropertyEqual(material.getName(), comparedMaterial.getName())) {
			return false;
		}
		if (!CheckPropertyEqual(material.getQuality(), comparedMaterial.getQuality())) {
			return false;
		}
		return true;
	}

	public static <T> boolean CheckPropertyEqual(T originProperty, T comparedProperty) {
		// 基本类型经过参数传递后会自动装箱
		String[] className = originProperty.getClass().getName().split("\\.");
		if (className[0].equals("java") && className[1].equals("math")) {
			return ((Comparable) originProperty).compareTo(comparedProperty) == 0 ? true : false;
		} else {
			return originProperty.equals(comparedProperty);
		}
	}
}

